
package com.ctrip.corp.obt.shard.holder;

import com.ctrip.corp.obt.generic.utils.StringPool;
import com.ctrip.corp.obt.shard.annotation.InnerPluginIgnore;
import lombok.Builder;
import lombok.Data;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;

/**
 * SQL 解析辅助类
 *
 * @author marco.zheng
 * @since 2022-03-15
 */
public abstract class InnerPluginIgnoreHolder {

    /**
     * SQL 解析缓存
     * key 可能是 mappedStatement 的 ID,也可能是 class 的 name
     */
    private static final Map<String, InnerPluginIgnoreCache> SQL_PARSER_IGNORE_CACHE = new ConcurrentHashMap<>();

    /**
     * 初始化缓存
     * <p>
     * Mapper 上 InnerPluginIgnore 注解信息
     *
     * @param mapperClass Mapper Class
     */
    public static synchronized InnerPluginIgnoreCache initSqlParserInfoCache(Class<?> mapperClass) {
        InnerPluginIgnore ignore = mapperClass.getAnnotation(InnerPluginIgnore.class);
        if (ignore != null) {
            String key = mapperClass.getName();
            InnerPluginIgnoreCache cache = buildInnerPluginIgnoreCache(ignore);
            SQL_PARSER_IGNORE_CACHE.put(key, cache);
            return cache;
        }
        return null;
    }

    /**
     * 初始化缓存
     * <p>
     * Mapper#method 上 InnerPluginIgnore 注解信息
     *
     * @param classAnnotation Mapper Class Name
     * @param method           Method
     */
    public static void initSqlParserInfoCache(InnerPluginIgnoreCache classAnnotation, String mapperClassName, Method method) {
        InnerPluginIgnore ignore = method.getAnnotation(InnerPluginIgnore.class);
        String key = mapperClassName.concat(StringPool.DOT).concat(method.getName());
        if (ignore != null) {
            InnerPluginIgnoreCache methodCache = buildInnerPluginIgnoreCache(ignore);
            if (classAnnotation == null) {
                SQL_PARSER_IGNORE_CACHE.put(key, methodCache);
                return;
            }
            SQL_PARSER_IGNORE_CACHE.put(key, chooseCache(classAnnotation, methodCache));
        }
    }

    public static boolean willIgnoreTenant(String id) {
        return willIgnore(id, InnerPluginIgnoreCache::getTenant);
    }

    public static boolean willIgnoreTableReroute(String id) {
        return willIgnore(id, InnerPluginIgnoreCache::getTableReroute);
    }

    public static boolean willIgnorePermission(String id) {
        return willIgnore(id, InnerPluginIgnoreCache::getPermission);
    }

    public static boolean willIgnoreAll(String id) {
        return willIgnore(id, InnerPluginIgnoreCache::getIgnoreAll);
    }

    private static boolean willIgnore(String id, Predicate<InnerPluginIgnoreCache> function) {
        InnerPluginIgnoreCache cache = SQL_PARSER_IGNORE_CACHE.get(id);
        if (cache == null) {
            cache = SQL_PARSER_IGNORE_CACHE.get(id.substring(0, id.lastIndexOf(StringPool.DOT)));
        }
        if (cache != null) {
            return function.test(cache);
        }
        return false;
    }

    private static InnerPluginIgnoreCache chooseCache(InnerPluginIgnoreCache mapper, InnerPluginIgnoreCache method) {
        return InnerPluginIgnoreCache.builder()
            .ignoreAll(chooseBoolean(mapper.getIgnoreAll(), method.getIgnoreAll()))
            .tenant(chooseBoolean(mapper.getTenant(), method.getTenant()))
            .tableReroute(chooseBoolean(mapper.getTableReroute(), method.getTableReroute()))
            .permission(chooseBoolean(mapper.getPermission(), method.getPermission()))
            .build();
    }

    private static InnerPluginIgnoreCache buildInnerPluginIgnoreCache(InnerPluginIgnore ignore) {
        return InnerPluginIgnoreCache.builder()
            .ignoreAll(ignore.ignoreAll())
            .tenant(ignore.tenant())
            .tableReroute(ignore.tableReroute())
            .permission(ignore.permission())
            .build();
    }

    /**
     * mapper#method 上的注解 优先级大于 mapper 上的注解
     */
    private static Boolean chooseBoolean(Boolean mapper, Boolean method) {
        if (mapper == null && method == null) {
            return method;
        }
        if (method != null) {
            return method;
        }
        return mapper;
    }

    private InnerPluginIgnoreHolder() {}

    @Data
    @Builder
    public static class InnerPluginIgnoreCache {
        private Boolean tenant;
        private Boolean tableReroute;
        private Boolean permission;
        private Boolean ignoreAll;
    }
}
